""" 
Usage:
	python svtinfo.py file [options] [read|edit params]
	
Examples:
	python svtinfo.py 0.svt -out test.txt -outfmt config
	python svtinfo.py 2.SVT -basic -out C:\SaveInfo\2.xml -outfmt xml
	python svtinfo.py 0.svt -notext -nocomment -out console
	python svtinfo.py 2.SVT -out console -readAll -edit gold=2523 pet_type=Cat retired=false
	python svtinfo.py 2.SVT -read name level difficulty pet_name
	python svtinfo.py "C:\My Saves\4.SVT"
	
Options:
	-out outfile.txt
		Specifies a different output file to dump the properties to. If not specified, the output file
		will be the input file with '.txt' appended. If -out is 'console' (case-insensitive), then the
		output will be printed to the console (useful for debugging).

	-outfmt TYPE
		How the output should be formatted, where TYPE is either:
			'props'  - Java-style properties files (default)
			'config' - Python-style config files
			'xml'    - XML output

	-notext
		If specified, certain text values (difficulty, cheater, hardcore, pet type) will be 
		substituted with their respective integer values (e.g. 0 for Easy difficulty). This may
		be useful for translating. See the "-notext & translating" section for details.
		
		Keep in mind, without text, all input for editing values must also be in "value" (non-text)
		form.

	-nocomment
		If specified, no 'generated by' comment will be added to the top of the output.

	-basic
		If 'basic' is enabled, the file will only seek three basic properties: class 
		name, player name and player level.
	
	-builtins builtins.cfg
		Allows you to pass a custom set of "built-in" values for pet_type and class name, overriding the
		hard-coded defaults. The file format is similar to Python's Config file format. A line starting with
		hash (`#`) or semi-colon (`;`) characters, ignoring trailing whitespace, is considered a comment and
		will be ignored. The pet_types section is denoted with `[PET_TYPES]`, and the class names section is
		denoted with `[CLASSES]`. The pet_types should be `key:value` pairs, where the key is the UNIT_GUID of
		the unit, and the value is the `DISPLAYNAME` of the unit. Separate each class name with a new line character
		(`\n`). An example file might look like this:
			
			[PET_TYPES]
			6655120081984033246:Cat
			-3195029593784249890:Dog
			1343088339509383646:Imaginary
			[CLASSES]
			Alchemist
			Vanquisher
			Destroyer
			Nether Mage
		
		Use the getbuiltins script to generate file based on masterresourceunits.dat.

	-linesep
		If specified, the output will use the system new line 
		separator, rather than the '\n' character

	-log logfile.txt
		Creates a log file and redirects standard output to it.

	-errlog errlogfile.txt
		Creates an error log file and redirects error output to it.

	-silent
		Nothing will be printed to the console, and no log files will be 
		created (even if -out is 'console').

	-tab
		XML output will use tab character (\t) instead of four spaces.
		
	-readAll 
		All properties will be read, regardless of the -edit or -read parameters. This is
		the default if no -edit or -read option is specified.
		
	-nogold
		Gold property will not be read.
	
	-nopet
		Pet properties will be not be read.

	-noencoding
		For XML files, the default encoding attribute will not be included in the <?xml?> header
		
Reading:
	-read
		If -read is specified, only those properties listed after it will be read. The other properties
		will be skipped. This has replaced the old -limit option.
		
		Properties are separated by spaces.
		
Editing:
	-edit
		If -edit is specified, any properties listed after it will be changed. Use "key=value" format to
		describe the changes. The "key" is the name of the property (e.g. "pet_type" or "class"), and the
		"value" is the desired change. Note that "value" is expected in text form unless the -notext 
		option is specified.
		
		For example, "retired=false" will set the retired flag in the SVT file to OFF.
		
		Properties are separated by spaces.
"""

import os
import sys
import svtio
from svtutil import odict, arg, indexof

class MainClass(object):
	silent = False
	options = None
	version = None
	appname = None
	argstr = None
	f = None
	ln = '\n'
	playerProps = None
	basicImpl = None
	propsList = None
	
	def __init__(self, version, args):
		self.argstr = str( args )
		self.version = version
		self.readOptions(args)
		
		#self.log('Running %s with args: %s' % (self.version, self.argstr))
		notext = self.options['notext']
		
		paths = self.options['paths']
		self.propsList = []
		
		#READ EACH PATH AS A SEPARATE SVT FILE...
		for p in paths:
			playerProps = self.copyProps(self.playerProps, odict())
			
			#open TL file
			self.f = svtio.TLPlayerSave(p, 'r+b')
			
			#if -notext isn't given, then assume it's all text...
			if not notext:
				self.f.text2values(playerProps)
			
			#determine whether we should read all
			readAll = len(playerProps) == 0
			
			#add file prop at start
			playerProps._keys.insert(0, 'file')
			playerProps._data['file'] = p
			
			self.f.nopet = self.options['nopet'] or False
			self.f.nogold = self.options['nogold'] or False
			readAll = readAll or self.options['readAll'] or False
			self.f.update(playerProps, readAll, self.basicImpl)
			self.f.close()
			if not notext:
				self.f.values2text(playerProps)
			self.propsList.append(playerProps)
		
		#START WRITING TO FILE....
		outfmt = self.options['outfmt']
		cmt = None
		text = ''
		#include a comment?
		if not self.options['nocomment']:
			import datetime
			now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
			cmt = '%s generated by %s %s on %s' % (outfmt, self.appname, self.version, now)
		
		if outfmt == 'xml':
			defenc = sys.getdefaultencoding()
			if cmt is not None:
				text += '<!-- %s -->%s' % (cmt, self.ln)
			enctxt = '' if self.options['noencoding'] else ' encoding="%s"' % defenc
			text += '<?xml version="1.0"%s?>%s' % (enctxt, self.ln)
			text += '<svtinfo version="%s">%s' % (self.version, self.ln)
		else:
			if cmt is not None:
				text += '#%s%s' % (cmt, self.ln)
		
		#WRITE EACH SAVE FILE INFO...
		for props in self.propsList:
			path = props['file'] 
			props['file'] = os.path.basename(path)
			path = os.path.abspath(path)
			text += self.writeNextProps(path, props, outfmt)
		text = text.strip()
		
		#OUTPUT ALL TEXT TO FILE/CONSOLE
		if outfmt == 'xml':
			text += '</svtinfo>'
		self.log(text)
		self.log()
		out = self.options['out']
		if out is not None:
			o = open(out, 'w')
			o.write(text)
			o.close()
	
	def writeNextProps(self, path, props, outfmt):
		t = '\t' if self.options['tab'] else '    '
		text = ''
		
		if outfmt=='xml':
			text += '%s<save path="%s">%s' % (t*2, path, self.ln)
		elif outfmt=='config':
			text += '[%s]%s' % (path, self.ln)
		else:
			text += 'path = %s%s' % (path, self.ln)
		
		for k, v in props.items():
			if v is not None:
				if outfmt == 'xml':
					text += '%s<%s>%s</%s>%s' % (t*3, k, self.escape(str(v)), k, self.ln)
				else:
					text += '%s = %s%s' % (k, v, self.ln)
		
		if outfmt=='xml':
			text += '%s</save>%s' % (t*2, self.ln)
		else:
			text += self.ln
		return text
	
	def escape(self, s):
		s = s.replace('&', '&amp;')
		s = s.replace('<', '&lt;')
		s = s.replace('>', '&gt;')
		return s
		
	def log(self, s=''):
		if not self.silent:
			print s 
	
	def copyProps(self, props, dest):
		for k, v in props.items():
			dest[k] = v
		return dest
	
	def readOptions(self, args):	
		self.options = {}
		self.appname = os.path.basename(args.pop(0))
		
		self.playerProps = odict()
		
		#logging info...
		self.options['silent'] = self.silent = '-silent' in args
		self.options['log'] = l = arg(args, '-log')
		self.options['errlog'] = el = arg(args, '-errlog')
		
		#if it's not silent
		if not self.options['silent']:
			if l is not None:
				sys.stdout = open(l, 'w') if not self.silent else None
			if el is not None:
				sys.stderr = open(el, 'w') if not self.silent else None
		
		self.options['linesep'] = '-linesep' in args
		self.ln = os.linesep if self.options['linesep'] else '\n'
		
		if '-basic' in args:
			self.options['basic'] = True
			self.playerProps['class'] = None
			self.playerProps['name'] = None
			self.playerProps['level'] = None
			
			def foo(save):
				save.next(4, 'class', save.STRING)
				save.next(81, 'name', save.STRING)
				save.next(84, 'level', save.INT)
			
			self.basicImpl = foo
		else:
			edInd = indexof(args, '-edit')
			rdInd = indexof(args, '-read')
			self.isEdit = edInd!=-1
			if self.isEdit and rdInd!=-1:
				raise Error("can't use -edit and -read together")
			elif self.isEdit or rdInd!=-1:
				ind = edInd if self.isEdit else rdInd
				propsArray = args[ind+1:]
				args = args[:ind]
				for pr in propsArray:
					k, v = pr.split('=') if self.isEdit else (pr, None)
					if v=='None':
						v = None
					self.playerProps[k] = v
		
		#get SVT path
		self.options['paths'] = []
		for a in args:
			if a[0] == '-':
				break
			else:
				self.options['paths'].append(a)
		if len(self.options['paths'])==0:
			self.log('No paths specified, format is: svtinfo.exe file [options]')
			self.log()
			raise Exception('No paths specified, format is: svtinfo.exe file [options]')
		
		#expand any directories
		pcopy = []
		for i, p in enumerate(self.options['paths']):
			if os.path.isdir(p):
				allFiles = os.listdir(p)
				for f in allFiles:
					ext = os.path.splitext(f)[-1].lower()
					if ext == '.svt':
						pcopy.append(os.path.join(os.path.abspath(p), f))
			else:
				pcopy.append(p)
		self.options['paths'] = pcopy
		
		#get options
		self.options['out'] = arg(args, '-out')
		self.options['outfmt'] = arg(args, '-outfmt', 'props').lower()
		self.options['notext'] = '-notext' in args
		self.options['noencoding'] = '-noencoding' in args
		self.options['tab'] = '-tab' in args
		self.options['nocomment'] = '-nocomment' in args
		self.options['nopet'] = '-nopet' in args
		self.options['nogold'] = '-nogold' in args
		self.options['readAll'] = '-readAll' in args
		self.options['builtins'] = arg(args, '-builtins')
		
		if self.options['builtins'] is not None:
			svtio.loadCustomBuiltins(self.options['builtins'], petDict=odict())
		
		if self.options['out'] is None:
			defOut = self.options['paths'][0] + '.txt'
			self.options['out'] = defOut
		elif self.options['out'].lower() == 'console':
			self.options['out'] = None
	
	
if __name__ == "__main__":
	version = 'v.1.1'
	MainClass(version, sys.argv)